/*
 * Copyright 2015 泛泛o0之辈
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.jfast.framework.jdbc.orm.sql;

import cn.jfast.framework.base.util.ClassUtils;
import cn.jfast.framework.base.util.StringUtils;
import cn.jfast.framework.jdbc.annotation.Select;
import cn.jfast.framework.jdbc.db.ConnectionFactory;
import cn.jfast.framework.base.util.ErrorCode;
import cn.jfast.framework.jdbc.orm.Executor;
import cn.jfast.framework.jdbc.orm.Record;
import cn.jfast.framework.jdbc.orm.RecordSet;
import cn.jfast.framework.jdbc.orm.SqlException;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.*;

public  class SelectSql extends Executor{

    private final Select select;
    private ResultSet rs;

    public SelectSql(Type[] paramTypes,
                     String[] paramNames,
                     Object[] args,
                     Select select,
                     Type returnType,
                     Method method) throws IllegalAccessException, SqlException, ClassNotFoundException, SQLException {
        this.method = method;
        this.paramNames = paramNames;
        this.paramTypes = paramTypes;
        this.args = args;
        this.select = select;
        this.returnType = returnType;
        conn = ConnectionFactory.getThreadLocalConnection();
        super.wrapParam();
    }

    @Override
    public Object execute() throws SqlException, IllegalAccessException, NoSuchFieldException, InstantiationException, SQLException {
        Object reObj = null;
        ps = conn.prepareStatement(parseSql());
        fillPreparedStatement();
        rs = ps.executeQuery();
        ResultSetMetaData meta = rs.getMetaData();
        int column = meta.getColumnCount();
        String[] columns = new String[column];
        int[] columntypes = new int[column];
        for (int i = 0; i < column; i++) {
            columns[i] = meta.getColumnLabel(i + 1);
            columntypes[i] = meta.getColumnType(i + 1);
        }
        if (returnType instanceof ParameterizedType) {        //如果是带泛型类型的参数
            ParameterizedType parameterizedType = (ParameterizedType) returnType;
            Type basicType = parameterizedType.getRawType();
            Type[] paramTypes = parameterizedType.getActualTypeArguments();
            if (basicType == List.class) {
                Type fType = paramTypes[0];
                Class<?> fClass = (Class<?>) fType;
                List<Object> result = new ArrayList<Object>();
                while (rs.next()) {
                    if (ClassUtils.isCommonTypeOrWrapper(fClass)) {
                        if (column > 1)
                            throw new SqlException(ErrorCode.MULTI_COLUMN);
                        else
                            result.add(rs.getObject(1));
                    } else if(fClass == Record.class){
						Record rc = new Record();
						for (int i = 0; i < column; i++) {
							rc.put(columns[i], rs.getObject(columns[i]));
						}
						result.add(rc);
					}  else {
                        Object tempObj = fClass.newInstance();
                        List<Field> fields = new ArrayList<Field>(Arrays.asList(fClass.getDeclaredFields()));
                        for (int i = 0; i < column; i++) {
                            Field field = null;
                            FieldLoop:
                            for (Field f : fields) {
                                if (f.getName().toLowerCase().equals(StringUtils.dbColumn2ModelColumn(columns[i]))) {
                                    field = f;
                                    fields.remove(f);
                                    break FieldLoop;
                                }
                            }
                            if (null != field) {
                                field.setAccessible(true);
                                field.set(tempObj,getFieldValue(field,rs.getObject(columns[i])));
                            }
                        }
                        result.add(tempObj);
                    }
                }
                reObj = result;
            } else if (basicType == Set.class) {
                Type fType = paramTypes[0];
                Class<?> fClass = (Class<?>) fType;
                Set<Object> result = new HashSet<Object>();
                while (rs.next()) {
                    if (ClassUtils.isCommonTypeOrWrapper(fClass)) {
                        if (column > 1)
                            throw new SqlException(ErrorCode.MULTI_COLUMN);
                        else
                            result.add(rs.getObject(1));
                    } else if(fClass == Record.class){
						Record rc = new Record();
						for (int i = 0; i < column; i++) {
							rc.put(columns[i], rs.getObject(columns[i]));
						}
						result.add(rc);
					}  else {
                        Object tempObj = fClass.newInstance();
                        List<Field> fields = new ArrayList<Field>(Arrays.asList(fClass.getDeclaredFields()));
                        for (int i = 0; i < column; i++) {
                            Field field = null;
                            FieldLoop:
                            for (Field f : fields) {
                                if (f.getName().toLowerCase().equals(StringUtils.dbColumn2ModelColumn(columns[i].toLowerCase()))) {
                                    field = f;
                                    fields.remove(f);
                                    break FieldLoop;
								}
							}
							if (null != field) {
								field.setAccessible(true);
								field.set(
										tempObj,
										getFieldValue(field,
												rs.getObject(columns[i])));
							}
						}
						result.add(tempObj);
					}
				}
				reObj = result;
			} else if (basicType == Map.class) {
				Type fType1 = paramTypes[0];
				if (fType1 != String.class)
					throw new SqlException(ErrorCode.ERROR_RETURN_TYPE);
				Map<String, Object> result = new LinkedHashMap<String, Object>();
				if (rs.next()) {
					for (int i = 0; i < column; i++)
						result.put(columns[i], rs
								.getObject(columns[i]));
					if (rs.next())
						throw new SqlException(ErrorCode.MULTI_ROW);
				}
				reObj = result;
			}
		} else {
            if (ClassUtils.isCommonTypeOrWrapper((Class<?>) returnType)) {
                if (column > 1)
                    throw new SqlException(ErrorCode.MULTI_COLUMN);
                if (rs.next()) {
                    reObj = rs.getObject(columns[0]);
                    if (rs.next())
                        throw new SqlException(ErrorCode.MULTI_ROW);
                }
            } else if (returnType == Record.class) {
            	Record result = new Record();
                if (rs.next()) {
                    for (int i = 0; i < column; i++)
                        result.put(columns[i], rs.getObject(columns[i]));
                    if (rs.next())
                        throw new SqlException(ErrorCode.MULTI_ROW);
                }
                reObj = result;
            } else if (returnType == RecordSet.class) {
            	RecordSet result = new RecordSet();
                while (rs.next()) {
                	Record record = new Record();
                    for (int i = 0; i < column; i++) {
                    	record.put(columns[i], rs.getObject(columns[i]));
                    }
                    result.addRecord(record);
                }
                reObj = result;
            } else if (returnType == List.class) {
                List<Object> result = new ArrayList<Object>();
                while (rs.next()) {
                    Map<String, Object> tempMap = new LinkedHashMap<String, Object>();
                    for (int i = 0; i < column; i++) {
                        tempMap.put(columns[i], rs.getObject(columns[i]));
                    }
                    result.add(tempMap);
                }
                reObj = result;
            } else if (returnType == Set.class) {
                Set<Object> result = new HashSet<Object>();
                while (rs.next()) {
                    Map<String, Object> tempMap = new LinkedHashMap<String, Object>();
                    for (int i = 0; i < column; i++) {
                        tempMap.put(columns[i], rs.getObject(columns[i]));
                    }
                    result.add(tempMap);
                }
                reObj = result;
            } else if (returnType == Map.class) {
                Map<String,Object> result = new LinkedHashMap<String,Object>();
                if (rs.next()) {
                    for (int i = 0; i < column; i++)
                        result.put(columns[i], rs.getObject(columns[i]));
                    if (rs.next())
                        throw new SqlException(ErrorCode.MULTI_ROW);
                }
                reObj = result;
            } else {
                Class<?> clazz = (Class<?>) returnType;
                List<Field> fields = new ArrayList<Field>(Arrays.asList(clazz.getDeclaredFields()));
                if (rs.next()) {
                    Object result = clazz.newInstance();
                    for (int i = 0; i < column; i++) {
                        Field field = null;
                        FieldLoop:
                        for (Field f : fields) {
                            if (f.getName().toLowerCase().equals(StringUtils.dbColumn2ModelColumn(columns[i]))) {
                                field = f;fields.remove(f);break FieldLoop;
                            }
                        }
                        if (null != field) {
                            field.setAccessible(true);
                            field.set(result, getFieldValue(field,rs.getObject(columns[i])));
                        }
                    }
                    reObj = result;
                    if (rs.next())
                        throw new SqlException(ErrorCode.MULTI_ROW);
                }
            }
        }
        if(conn.getAutoCommit() == true && !conn.isClosed())
            conn.close();
        return reObj;
    }

    @Override
    public String getSql() {
        return select.sql()+" ";
    }
}
